package services

import (
	"crypto/md5"
	"crypto/rand"
	"encoding/base64"
	"encoding/hex"
	"io"
	"pegasus/datatypes"
	"sync"
)

//SessionService 管理Session
type SessionService interface {
	Remove(token string)
	ForceRemove(userID int64) datatypes.SIContent
	Add(userID int64) string
	Update(token string) datatypes.SIContent
	Has(userID int64) bool
}

type sessionService struct {
	tokens   map[string]datatypes.SIContent
	rwLocker *sync.RWMutex
}

//NewSessionService 返回SessionService
func NewSessionService() SessionService {
	//需要启动一个routine来定时去除过期的token
	return &sessionService{
		tokens:   make(map[string]datatypes.SIContent),
		rwLocker: &sync.RWMutex{},
	}
}

func (si *sessionService) Add(userID int64) string {
	si.rwLocker.Lock()
	defer si.rwLocker.Unlock()
	var id string
	for k, v := range si.tokens {
		if v.User() == userID {
			id = k
		}
	}
	if id != "" {
		//删除之前的GUID
		delete(si.tokens, id)
	}

	for {
		id = genGUID()
		if _, ok := si.tokens[id]; !ok {
			break
		}
	}

	si.tokens[id] = datatypes.NewSIContent(id, userID)
	return id
}

func (si *sessionService) ForceRemove(userID int64) datatypes.SIContent {
	si.rwLocker.Lock()
	defer si.rwLocker.Unlock()
	var token string
	var content datatypes.SIContent
	for k, v := range si.tokens {
		if v.User() == userID {
			token = k
			content = v
			break
		}
	}
	if token != "" {
		delete(si.tokens, token)
	}
	return content
}

func (si *sessionService) Remove(guid string) {
	si.rwLocker.Lock()
	defer si.rwLocker.Unlock()
	delete(si.tokens, guid) //测试下 不存在的guid 是否会引发异常
}

func (si *sessionService) Update(token string) datatypes.SIContent {
	si.rwLocker.RLock()
	defer si.rwLocker.RUnlock()
	return si.tokens[token]
}

func (si *sessionService) Has(userID int64) bool {
	si.rwLocker.RLock()
	defer si.rwLocker.RUnlock()
	for _, v := range si.tokens {
		if v.User() == userID {
			return true
		}
	}
	return false
}

func genGUID() string {
	b := make([]byte, 48)
	if _, err := io.ReadFull(rand.Reader, b); err != nil {
		return ""
	}
	h := md5.New()
	h.Write([]byte(base64.URLEncoding.EncodeToString(b)))
	return hex.EncodeToString(h.Sum(nil))
}
